package config

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/ochinchina/supervisord/lib"
	"github.com/ochinchina/supervisord/tool"
	log "github.com/sirupsen/logrus"
)

//
// Load load the configuration and return the loaded programs
func (c *Config) Load2(configFile string) ([]string, error) {
	cfg := lib.NewConfig(configFile)

	c.configFile = configFile
	c.ProgramGroup = NewProcessGroup()
	c.Config = cfg
	log.WithFields(log.Fields{"file": c.configFile}).Info("load configuration from file")

	ret := c.parse2(cfg)

	progConfigs := make([]*Entry, 0)
	for _, en := range c.entries {
		fmt.Println("\n------------------" + en.Name)
		for k, v := range en.keyValues {
			fmt.Println(k + " = " + v)
		}
		if strings.HasPrefix(en.Name, "program:") {
			progConfigs = append(progConfigs, en)
		}
	}
	fmt.Println()

	// monitor.RunMonitor()

	// time.Sleep(100 * time.Second)

	// result := sortProgram(progConfigs)
	// for _, e := range result {
	// 	fmt.Printf("-----------------------%s\n", e.GetProgramName())
	// }

	// if c.configFile != "" {
	// 	os.Exit(1)
	// }

	return ret, nil
}

func (c *Config) parse2(cfg *lib.Config) []string {
	c.parseGroup2(cfg)
	loadedPrograms := c.parseProgram2(cfg)
	// parse non-group,non-program and non-eventlistener sections
	{
		entry := c.createEntry("supervisord", c.GetConfigFileDir())
		entry.parse3("supervisord", *cfg.Buddhad)
	}
	{
		entry := c.createEntry("inet_http_server", c.GetConfigFileDir())
		entry.parse3("inet_http_server", *cfg.InetHttpServer)
	}
	{
		entry := c.createEntry("supervisorctl", c.GetConfigFileDir())
		entry.parse3("supervisorctl", *cfg.BuddhadCtl)
	}
	{
		entry := c.createEntry("unix_http_server", c.GetConfigFileDir())
		entry.parse3("unix_http_server", *cfg.UnixHttpServer)
	}
	return loadedPrograms
}

func (c *Config) parseGroup2(cfg *lib.Config) {
	// parse the group at first
	for _, section := range cfg.Groups {
		entry := c.createEntry("group:"+section.Name, c.GetConfigFileDir())

		entry.parse3("group:"+section.Name, section)
		entry.keyValues["priority"] = fmt.Sprintf("%v", section.Order)

		groupName := section.Name
		programs := entry.GetPrograms()
		for _, program := range programs {
			c.ProgramGroup.Add(groupName, program)
		}
	}
}

// parse the sections starts with "program:" prefix.
//
// Return all the parsed program names in the ini
func (c *Config) parseProgram2(cfg *lib.Config) []string {
	defaultProgram := lib.GetDefaultProgram()
	for _, section := range cfg.Programs {
		if section.Name == "default" {
			defaultProgram = section
		}
	}

	loadedPrograms := make([]string, 0)

	prefix := "program:"
	for k, section := range cfg.Programs {
		newp, err := copy(section, defaultProgram)
		if err == nil {
			section = *newp
		}
		// get the number of processes
		numProcs := section.Numprocs
		programName := section.Name
		procName := section.ProcessName

		if numProcs < 1 {
			numProcs = 1
		} else if numProcs > 1 {
			if strings.Index(procName, "%(process_num)") == -1 {
				log.WithFields(log.Fields{
					"numprocs":     numProcs,
					"process_name": procName,
				}).Error("no process_num in process name")
			}
		}
		originalProcName := programName
		if originalProcName == "" {
			originalProcName = procName
		}

		originalCmd := section.Command

		for i := 1; i <= numProcs; i++ {
			envs := tool.NewStringExpression("program_name", programName,
				"process_num", fmt.Sprintf("%d", i),
				"group_name", c.ProgramGroup.GetGroup(programName, programName),
				"here", c.GetConfigFileDir())

			if section.Environment != nil {
				for _, envstr := range section.Environment {
					ev := strings.SplitN(envstr, "=", 2)
					if len(ev) == 2 {
						envs.Add(fmt.Sprintf("ENV_%s", strings.TrimSpace(ev[0])), strings.TrimSpace(ev[1]))
					}
				}
			}

			cmd, err := envs.Eval(originalCmd)
			if err != nil {
				log.WithFields(log.Fields{
					log.ErrorKey: err,
					"program":    programName,
				}).Error("get envs failed")
				continue
			}

			procName, err := envs.Eval(originalProcName)
			if err != nil {
				log.WithFields(log.Fields{
					log.ErrorKey: err,
					"program":    programName,
				}).Error("get envs failed")
				continue
			}
			section.Command = cmd
			section.ProcessName = procName
			section.ProcessNum = i
			section.NumprocsStart = i - 1
			if section.Environment == nil {
				// section.Environment = []string{"AAA=BB", "CC=DD"}
			}

			cfg.Programs[k] = section

			entry := c.createEntry(procName, c.GetConfigFileDir())
			entry.parse3(prefix+procName, cfg.Programs[k])
			group := c.ProgramGroup.GetGroup(programName, programName)
			entry.Group = group

			entry.keyValues["command"] = section.StartCmd
			entry.keyValues["stopasgroup"] = fmt.Sprintf("%v", section.Stopchildren)
			entry.keyValues["killasgroup"] = fmt.Sprintf("%v", section.Killchildren)
			entry.keyValues["priority"] = fmt.Sprintf("%v", section.Order)
			entry.Program = &cfg.Programs[k]
			entry.Config = cfg

			loadedPrograms = append(loadedPrograms, procName)
		}
	}

	return loadedPrograms
}

// func (c *Entry) parse2(name string, section lib.ProgramConfig) {
// 	c.Name = name
// 	var typeInfo = reflect.TypeOf(section)
// 	var valInfo = reflect.ValueOf(section)
// 	num := typeInfo.NumField()
// 	for i := 0; i < num; i++ {
// 		a := strings.TrimSpace(fmt.Sprintf("%v", valInfo.Field(i).Interface()))
// 		if strings.HasPrefix(a, "[") && strings.HasSuffix(a, "]") {
// 			a = strings.ReplaceAll(a, " ", ",")
// 			a = a[1 : len(a)-1]
// 		}
// 		c.keyValues[lib.Camel2Case(typeInfo.Field(i).Name)] = a
// 	}
// }

func (c *Entry) parse3(name string, section interface{}) {
	c.Name = name
	var typeInfo = reflect.TypeOf(section)
	var valInfo = reflect.ValueOf(section)
	num := typeInfo.NumField()
	for i := 0; i < num; i++ {
		a := strings.TrimSpace(fmt.Sprintf("%v", valInfo.Field(i).Interface()))
		if strings.HasPrefix(a, "[") && strings.HasSuffix(a, "]") {
			a = strings.ReplaceAll(a, " ", ",")
			a = a[1 : len(a)-1]
		}
		if a != "" {
			c.keyValues[lib.Camel2Case(typeInfo.Field(i).Name)] = a
		}
	}
}

func copy(c1, c2 lib.ProgramConfig) (*lib.ProgramConfig, error) {
	v1 := reflect.ValueOf(c1)              //初始化为c1保管的具体值的v1
	v2 := reflect.ValueOf(c2)              //初始化为c2保管的具体值的v2
	v1_elem := reflect.ValueOf(&c1).Elem() //返回 c1 指针保管的值

	for i := 0; i < v1.NumField(); i++ {
		field := v1.Field(i)  //返回结构体的第i个字段
		field2 := v2.Field(i) //返回结构体的第i个字段
		//field.Interface() 当前持有的值
		//reflect.Zero 根据类型获取对应的 零值
		//这个必须调用 Interface 方法 否则为 reflect.Value 构造体的对比 而不是两个值的对比
		//这个地方不要用等号去对比 因为golang 切片类型是不支持 对比的
		if reflect.DeepEqual(field.Interface(), reflect.Zero(field.Type()).Interface()) {
			//如果第一个构造体某个字段对应类型的默认值

			if !reflect.DeepEqual(field2.Interface(), reflect.Zero(field2.Type()).Interface()) {
				//如果第二个构造体 这个字段不为空

				if !v1_elem.Field(i).CanSet() { //如果不可以设置值 直接返回
					fmt.Println("not set value")
					return nil, errors.New("can not set value")
				}

				v1_elem.Field(i).Set(field2) //设置值
			}
		}
	}
	return &c1, nil
}
